// OpentTxl-C Version 11 rule table
// J.R. Cordy, Jan 2023

// Copyright 2023, James R. Cordy and others

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the “Software”), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
// AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The OpenTxl compiled rule table
// The TXL rule compiler stores the compiled rules and functions of the TXL program in this table,
// organized into separate stores for rule parts (constructors, deconstructors, etc.) and
// rule local variables in order to avoid artificial limits on any particular rule. 

// Modification Log

// v11.0 Initial revision, adapted from OpenTxl 11.0

// v11.1 Added new predefined function [faccess]
//       Updated [system] predefined function to return success code for use in where clauses

// v11.3 Added multiple skipping criteria for both patterns and deconstructors
//       Changed behaviour of [+] to treat [srclinenumber] as [number]

// I/O, strings, memory allocation
#include "support.h"

// Global modules
#include "limits.h"
#include "tokens.h"
#include "trees.h"
#include "shared.h"
#include "idents.h"
#include "errors.h"

// Check interface consistency
#include "rules.h"

// The TXL Rule Table
int maxTotalRuleParameters; // maxRules * avgParameters
int maxTotalRuleLocals;     // maxTotalRuleParameters + maxRules * avgLocalVars

// Rule parts
int maxTotalRuleParts;      // maxRules * avgParts

// Rule calls
int maxTotalRuleCalls;      // maxTotalRuleParts (average one call per part)

// The main rule of the transformation
int mainRule;           // 0

// The rule table
struct array (ruleT, rule_rules);
int rule_nRules;        // 0

// Rule local variables, rule parts (constructors, deconstructors, etc.), and rule calls
// are all stored indirectly as frames in a large single array containing all of them for all rules.
// This avoids artificial limits on the size of rules by allowing for rules with hundreds of locals, parts or calls.

// The rule local variables table

// The local variables for all rules are stored as contiguous frames in this single array, 
// defined for each rule by a zero-origin base element index (rule.localVars.localsBase), 
// the number of rule formal parameters (rule.localVars.nformals),
// the number of pre-pattern local variables (rule.localVars.nprelocals), 
// the number of total local variables (rule.localVars.nlocals), including the above.

// Because rules may be used before being defined, the table is organized into 
// a temporary inferred formals space, used to hold the inferred parameter type frames 
// of rules that have been used but not yet defined, and the actual locals space, 
// used for the complete locals frames of rules when they are defined.

// When a used rule is defined, its formal types are compared to the inferred types
// in its temporary inferred formals frame to check for consistency.

struct array (ruleLocalT, rule_ruleLocals);
int rule_ruleFormalCount;       // 0
int rule_ruleLocalCount;        // maxTotalRuleParameters (leave space reserved for temporary formals info)

// The rule parts table

// The rule parts (constructors, deconstructors, where clauses, etc.)
// for all rules are stored as sequential contiguous frames in this single array,
// defined for each rule by a zero-origin index for each of the pre-pattern (rule.prePattern.partsBase) 
// and post-pattern (rule.postPattern.partsBase) and their respective counts (rule.prePattern.nparts, 
// rule.postPattern.nparts). 

struct array (rulePartT, rule_ruleParts);
int rule_rulePartCount;         // 0

// The rule calls table
// Used only by the rule compiler, to optimize tree sharing and avoid copying of trees

// The set of called rules for all rules are stored as contiguous frames in this single array,
// defined for each rule by a zero-origin index (rule.calledRules.callsBase) and a count (rule.calledRules.ncalls).

unsigned array (short, rule_ruleCalls);
int rule_ruleCallCount;         // 0

#ifndef NOCOMPILE 

// Operations on the rule table

int rule_enterRule (const tokenT ruleName)
{
    for (int r = 1; r <= rule_nRules; r++) {
        if (rule_rules[r].name == ruleName) {
            return (r);
        }
    }

    if (rule_nRules == maxRules) {
        string message;
        stringprintf (message, "Too many rule/function definitions (> %d)", maxRules);
        error ("", message, LIMIT_FATAL, 531);
    }

    rule_nRules++;

    struct ruleT *r = &(rule_rules[rule_nRules]);
    r->name = ruleName;
    r->localVars.nformals = 0;
    r->localVars.nlocals = 0;
    r->localVars.nprelocals = 0;
    r->localVars.localsBase = rule_ruleLocalCount;
    r->calledRules.ncalls = 0;
    r->calledRules.callsBase = rule_ruleCallCount;
    r->target = NOT_FOUND;
    r->skipName = NOT_FOUND;
    r->skipName2 = NOT_FOUND;
    r->skipName3 = NOT_FOUND;
    r->prePattern.nparts = 0;
    r->prePattern.partsBase = rule_rulePartCount;
    r->postPattern.nparts = 0;
    r->postPattern.partsBase = rule_rulePartCount;
    r->patternTP = nilTree;
    r->replacementTP = nilTree;
    r->kind = ruleKind_rule;
    r->defined = false;
    r->called = false;
    r->starred = false;
    r->isCondition = false;
    r->skipRepeat = false;

    return (rule_nRules);
}

int rule_lookupLocalVar (const string context, const struct ruleLocalsT *localVars, const tokenT varName)
{
    for (int i = 1; i <= localVars->nlocals; i++) {
        if (rule_ruleLocals[localVars->localsBase + i].name == varName) {
            return (i);
        }
    }

    return (0);
}

int rule_findLocalVar (const string context, const struct ruleLocalsT *localVars, const tokenT varName)
{
    for (int i = 1; i <= localVars->nlocals; i++) {
        if (rule_ruleLocals[localVars->localsBase + i].name == varName) {
            return (i);
        }
    }

    string message;
    stringprintf (message, "Variable '%s' has not been defined", *ident_idents[varName]);
    error (context, message, FATAL, 532);

    return (0);
}

int rule_enterLocalVar (const string context, struct ruleLocalsT *localVars, const tokenT varName, const tokenT varType)
{
    string message;

    for (int i = 1; i <= localVars->nlocals; i++) {
        if (rule_ruleLocals[localVars->localsBase + i].name == varName) {
            stringprintf (message, "Variable '%s' has already been defined", *ident_idents[varName]);
            error (context, message, FATAL, 533);
        }
    }

    if (localVars->nlocals == maxLocalVars) {
        error (context, "Rule/function is too complex - simplify using subrules", LIMIT_FATAL, 530);
    }

    if (rule_ruleLocalCount == maxTotalRuleLocals) {
        stringprintf (message,  "Too many total local variables in rules of TXL program (> %d)", maxTotalRuleLocals);
        error (context, message, LIMIT_FATAL, 534);
    }

    rule_ruleLocalCount++;
    localVars->nlocals++;

    struct ruleLocalT *localVar = &(rule_ruleLocals[localVars->localsBase + localVars->nlocals]);
    localVar->name = varName;
    localVar->typename_ = varType;
    localVar->basetypename = varType;

    string *varTypeName = (string *) (ident_idents[varType]);
    string varBaseTypeName;

    if (stringncmp (*varTypeName, "repeat_1_", 9) == 0) {
        // base type of [repeat_1_X] is [repeat_0_X]
        stringcpy (varBaseTypeName, *varTypeName);
        stringchar (varBaseTypeName, 8) = '0';
        localVar->basetypename = ident_lookup (varBaseTypeName);

    } else if (stringncmp (*varTypeName, "list_1_", 7) == 0) {
        // base type of [list_1_X] is [list_0_X]
        stringcpy (varBaseTypeName, *varTypeName);
        stringchar (varBaseTypeName, 6) = '0';
        localVar->basetypename = ident_lookup (varBaseTypeName);
    }

    localVar->refs = 0;
    localVar->changed = false;
    localVar->global = false;
    localVar->partof = 0;
    localVar->lastref = nilTree;

    return (localVars->nlocals);
}

void rule_unenterLocalVar (const string context, struct ruleLocalsT *localVars, const tokenT varName)
{
    assert (rule_ruleLocals[localVars->localsBase + localVars->nlocals].name == varName);
    localVars->nlocals--;
    rule_ruleLocalCount--;
}

void rule_enterRuleCall (const string context, const int callingRuleIndex, const int calledRuleIndex)
{
    struct ruleCallsT *calls = &(rule_rules[callingRuleIndex].calledRules);

    for (int c = 1; c <= calls->ncalls; c++) {
        if (rule_ruleCalls[calls->callsBase + c] == calledRuleIndex) {
            return;
        }
    }

    if (calls->ncalls == maxRuleCalls) {
        error (context, "Rule/function is too complex - simplify using subrules", LIMIT_FATAL, 530);
    }

    if (rule_ruleCallCount == maxTotalRuleCalls) {
        string message;
        stringprintf (message, "Too many total rule calls in rules of TXL program (> %d)", maxTotalRuleCalls);
        error (context, message, LIMIT_FATAL, 535);
    }

    rule_ruleCallCount++;
    calls->ncalls++;
    rule_ruleCalls[calls->callsBase + calls->ncalls] = calledRuleIndex;
}

void rule_cloneRule (const int newIndex, const int oldIndex)
{
    structassign (rule_rules[newIndex], rule_rules[oldIndex]);
}

void rule_setCalled (const int ruleIndex, const bool setting)
{
    rule_rules[ruleIndex].called = setting;
}

void rule_setDefined (const int ruleIndex, const bool setting)
{
    rule_rules[ruleIndex].defined = setting;
}

void rule_setIsCondition (const int ruleIndex, const bool setting)
{
    rule_rules[ruleIndex].isCondition = setting;
}

void rule_setKind (const int ruleIndex, const enum ruleKindT kind)
{
    rule_rules[ruleIndex].kind = kind;
}

void rule_setPattern (const int ruleIndex, const treePT patternTP)
{
    rule_rules[ruleIndex].patternTP = patternTP;
}

void rule_setReplacement (const int ruleIndex, const treePT replacementTP)
{
    rule_rules[ruleIndex].replacementTP = replacementTP;
}

void rule_setSkipRepeat (const int ruleIndex, const bool setting)
{
    rule_rules[ruleIndex].skipRepeat = setting;
}

void rule_setStarred (const int ruleIndex, const bool setting)
{
    rule_rules[ruleIndex].starred = setting;
}

void rule_setTarget (const int ruleIndex, const tokenT typeName)
{
    rule_rules[ruleIndex].target = typeName;
}

void rule_setLocalsBase (const int ruleIndex, const int localsBase)
{
    rule_rules[ruleIndex].localVars.localsBase = localsBase;
}

void rule_setNFormals (const int ruleIndex, const int nformals)
{
    rule_rules[ruleIndex].localVars.nformals = nformals;
}

void rule_setNPreLocals (const int ruleIndex, const int nprelocals)
{
    rule_rules[ruleIndex].localVars.nprelocals = nprelocals;
}

void rule_setNLocals (const int ruleIndex, const int nlocals)
{
    rule_rules[ruleIndex].localVars.nlocals = nlocals;
}

void rule_setCallsBase (const int ruleIndex, const int callsBase)
{
    rule_rules[ruleIndex].calledRules.callsBase = callsBase;
}

void rule_setNCalls (const int ruleIndex, const int ncalls)
{
    rule_rules[ruleIndex].calledRules.ncalls = ncalls;
}

void rule_setPrePatternPartsBase (const int ruleIndex, const int partsBase)
{
    rule_rules[ruleIndex].prePattern.partsBase = partsBase;
}

void rule_setPrePatternNParts (const int ruleIndex, const int nparts)
{
    rule_rules[ruleIndex].prePattern.nparts = nparts;
}

void rule_setPostPatternPartsBase (const int ruleIndex, const int partsBase)
{
    rule_rules[ruleIndex].postPattern.partsBase = partsBase;
}

void rule_setPostPatternNParts (const int ruleIndex, const int nparts)
{
    rule_rules[ruleIndex].postPattern.nparts = nparts;
}

void rule_setSkipName (const int ruleIndex, const tokenT name)
{
    if (rule_rules[ruleIndex].skipName == NOT_FOUND) {
        rule_rules[ruleIndex].skipName = name;
    } else if (rule_rules[ruleIndex].skipName2 == NOT_FOUND) {
        rule_rules[ruleIndex].skipName2 = name;
    } else {
        rule_rules[ruleIndex].skipName3 = name;
    }
}

void rule_incLocalCount (const int increment)
{
    rule_ruleLocalCount += increment;
}

void rule_incFormalCount (const int increment)
{
    rule_ruleFormalCount += increment;
}

void rule_incPartCount (const int increment)
{
    rule_rulePartCount += increment;
}

void rule_cloneLocalVar (const int newLocalIndex, const int oldLocalIndex)
{
    structassign (rule_ruleLocals[newLocalIndex], rule_ruleLocals[oldLocalIndex]);
}

void rule_incLocalRefs (const int localIndex, const int increment)
{
    rule_ruleLocals[localIndex].refs += increment;
}

void rule_setLocalChanged (const int localIndex, const bool setting)
{
    rule_ruleLocals[localIndex].changed = setting;
}

void rule_setLocalGlobal (const int localIndex, const bool setting)
{
    rule_ruleLocals[localIndex].global = setting;
}

void rule_setLocalLastRef (const int localIndex, const treePT lastref)
{
    rule_ruleLocals[localIndex].lastref = lastref;
}

void rule_setLocalName (const int localIndex, const tokenT name)
{
    rule_ruleLocals[localIndex].name = name;
}

void rule_setLocalPartOf (const int localIndex, const int nameRef)
{
    rule_ruleLocals[localIndex].partof = nameRef;
}

void rule_setLocalRefs (const int localIndex, const int refs)
{
    rule_ruleLocals[localIndex].refs = refs;
}

void rule_setLocalType (const int localIndex, const tokenT typeName)
{
    rule_ruleLocals[localIndex].typename_ = typeName;
}

void rule_setPartKind (const rulePartsBaseT partIndex, const enum rulePartKindT kind)
{
    rule_ruleParts[partIndex].kind = kind;
}

void rule_setPartName (const rulePartsBaseT partIndex, const tokenT name)
{
    rule_ruleParts[partIndex].name = name;
}

void rule_setPartNameRef (const rulePartsBaseT partIndex, const int nameRef)
{
    rule_ruleParts[partIndex].nameRef = nameRef;
}

void rule_setPartTarget (const rulePartsBaseT partIndex, const tokenT typeName)
{
    rule_ruleParts[partIndex].target = typeName;
}

void rule_setPartPattern (const rulePartsBaseT partIndex, const treePT patternTP)
{
    rule_ruleParts[partIndex].patternTP = patternTP;
}

void rule_setPartReplacement (const rulePartsBaseT partIndex, const treePT replacementTP)
{
    rule_ruleParts[partIndex].replacementTP = replacementTP;
}

void rule_setPartNegated (const rulePartsBaseT partIndex, const bool setting)
{
    rule_ruleParts[partIndex].negated = setting;
}

void rule_setPartAnded (const rulePartsBaseT partIndex, const bool setting)
{
    rule_ruleParts[partIndex].anded = setting;
}

void rule_setPartStarred (const rulePartsBaseT partIndex, const bool setting)
{
    rule_ruleParts[partIndex].starred = setting;
}

void rule_setPartSkipName (const rulePartsBaseT partIndex, const tokenT name)
{
    if (rule_ruleParts[partIndex].skipName == NOT_FOUND) {
        rule_ruleParts[partIndex].skipName = name;
    } else if (rule_ruleParts[partIndex].skipName2 == NOT_FOUND) {
        rule_ruleParts[partIndex].skipName2 = name;
    } else {
        rule_ruleParts[partIndex].skipName3 = name;
    }
}

void rule_setPartSkipRepeat (const rulePartsBaseT partIndex, const bool setting)
{
    rule_ruleParts[partIndex].skipRepeat = setting;
}

void rule_setPartGlobalRef (const rulePartsBaseT partIndex, const treePT ref)
{
    rule_ruleParts[partIndex].globalRef = ref;
}

// Predefined functions of TXL

void rule_checkPredefinedFunctionScopeAndParameters (const string context, const int ruleIndex, 
        const tokenT scopetype, const tokenT p1type, const tokenT p2type)
{
    assert (rule_rules[ruleIndex].kind == ruleKind_predefined);

    switch (ruleIndex) {
        case spliceR:
            {
                // Generic repeat splice or append 
                // Repeat1 [. Repeat2]  or  Repeat1 [. Element2] 
                string sname;
                stringcpy (sname, *ident_idents[scopetype]);

                // we can only splice to a repeat
                if (stringindex (sname, "repeat_") != 1) {
                    error (context, "Scope of [.] predefined function is not a repeat", FATAL, 536);
                }

                // get the element type of the repeat
                string X, repeatX, repeat1X;
                substring (X, sname, 10, stringlen (sname));
                stringcpy (repeatX, "repeat_0_"), stringcat (repeatX, X);
                stringcpy (repeat1X, "repeat_1_"), stringcat (repeat1X, X);
                const int Xindex = ident_lookup (X);
                const int repeatIndex = ident_lookup (repeatX);
                const int repeat1Index = ident_lookup (repeat1X);

                // can only splice elements or repeats onto repeats 
                if ((p1type != Xindex) && (p1type != repeatIndex) && (p1type != repeat1Index)) {
                    error (context, "Parameter of [.] predefined function does not match scope type", FATAL, 537);
                }
            }
            break;

        case listSpliceR:
            {
                // Generic list splice or append 
                // List1 [. List2]  or  List1 [. Element2] 
                string  sname;
                stringcpy (sname, *ident_idents[scopetype]);

                // we can only splice to a list
                if (stringindex (sname, "list_") != 1) {
                    error (context, "Scope of [,] predefined function is not a list", FATAL, 536);
                }

                // get the element type of the list
                string X, listX, list1X;
                substring (X, sname, 8, stringlen (sname));
                stringcpy (listX, "list_0_"), stringcat (listX, X);
                stringcpy (list1X, "list_1_"), stringcat (list1X, X);
                const int Xindex = ident_lookup (X);
                const int listIndex = ident_lookup (listX);
                const int list1Index = ident_lookup (list1X);

                // can only splice elements or lists onto lists 
                if ((p1type != Xindex) && (p1type != listIndex) && (p1type != list1Index)) {
                    error (context, "Parameter of [.] predefined function does not match scope type", FATAL, 537);
                }
            }
            break;

        case addR: case subtractR: case multiplyR: case divideR: case divR: case remR:
            {
                // N1 [+ N2]    N1 := N1 + N2
                // N1 [- N2]    N1 := N1 - N2
                // N1 [* N2]    N1 := N1 * N2
                // N1 [/ N2]    N1 := N1 / N2
                // N1 [div N2]  N1 := N1 div N2
                // N1 [rem N2]  N1 := N1 rem N2
                tokenT resulttype = number_T;

                if ((scopetype == number_T) || (scopetype == floatnumber_T) || (scopetype == decimalnumber_T)
                        || (scopetype == integernumber_T)) {
                    resulttype = number_T;
                } else if (scopetype == stringlit_T) {
                    resulttype = stringlit_T;
                } else if (scopetype == charlit_T) {
                    resulttype = charlit_T;
                } else if ((scopetype == id_T) || (scopetype == upperid_T) || (scopetype == lowerid_T) 
                        || (scopetype == upperlowerid_T) || (scopetype == lowerupperid_T)) {
                    resulttype = id_T;  // equivalence class
                } else if ((typeKind (scopetype) > firstLeafKind) 
                        && (typeKind (scopetype) <= lastLeafKind)) {
                    resulttype = scopetype;
                } else {
                    error (context, "Scope of [+], [-], [*], [/], [div] or [rem] is not a number, string or token", FATAL, 540);
                }

                bool ok = true;

                if ((p1type == number_T) || (p1type == floatnumber_T) || (p1type == decimalnumber_T)
                        || (p1type == integernumber_T) || (p1type == srclinenumber_T)) {
                    if ((resulttype != number_T) && (ruleIndex != addR)) {
                        ok = false;
                    }

                } else {
                    if ((typeKind (p1type) > firstLeafKind) && (typeKind (p1type) <= lastLeafKind)) {
                        if ((resulttype == number_T) || (ruleIndex != 1)) {
                            ok = false;
                        }
                    } else {
                        ok = false;
                    }
                }

                if (!ok) {
                    error (context, "Parameter of [+], [-], [*], [/], [div] or [rem] predefined function does not match scope type", FATAL, 541);
                }
            }
            break;

        case substringR:
            {
                if ((typeKind (scopetype) <= firstLeafKind) 
                        || (typeKind (scopetype) > lastLeafKind)) {
                    error (context, "Scope of [:] predefined function is not a string, identifier or token", FATAL, 542);
                }
        
                bool ok = true;

                if ((p1type != number_T) && (p1type != floatnumber_T) && (p1type != decimalnumber_T) 
                        && (p1type != integernumber_T)) {
                    ok = false;
                }

                if ((p2type != number_T) && (p2type != floatnumber_T) && (p2type != decimalnumber_T) 
                        && (p2type != integernumber_T)) {
                    ok = false;
                }

                if (!ok) {
                    error (context, "Parameters of [:] predefined function are not numbers", FATAL, 543);
                }
            }
            break;

        case lengthR:
            {
                if ((scopetype != number_T) && (scopetype != floatnumber_T) && (scopetype != decimalnumber_T) 
                        && (scopetype != integernumber_T)) {
                    error (context, "Scope of [#] predefined function is not a number", FATAL, 544);
                }

                if ((typeKind (p1type) <= firstLeafKind) || (typeKind (p1type) > lastLeafKind)) {
                    error (context, "Parameter of [#] predefined function is not a string, identifier or token", FATAL, 545);
                };

            }
            break;

        case equalR: case notEqualR:
            {
                // N1 [= N2]    N1 = N2
                // N1 [~= N2]   N1 ~= N2
                // (defined as equality on numbers and identity on all other types)
                bool ok = true;

                if ((scopetype == number_T) || (scopetype == floatnumber_T) || (scopetype == decimalnumber_T) 
                        || (scopetype == integernumber_T)) {
                    if ((p1type != number_T) && (p1type != floatnumber_T) && (p1type != decimalnumber_T) 
                            && (p1type != integernumber_T)) {
                        ok = false;
                    }
                } else if ((typeKind (scopetype) > firstLeafKind) 
                        && (typeKind (scopetype) <= lastLeafKind)) {
                    if ((typeKind (p1type) <= firstLeafKind) && (typeKind (p1type) > lastLeafKind)) {
                        ok = false;
                    }
                } else if (p1type != scopetype) {
                    ok = false;
                }

                if (!ok) {
                    error (context, "Parameter of [=] or [~=] predefined function does not match scope type", FATAL, 546);
                }
            }
            break;

        case lessR: case lessEqualR: case greaterR: case greaterEqualR:
            {
                // N1 [> N2]    N1 > N2
                // N1 [< N2]    N1 < N2
                // N1 [<= N2]   N1 <= N2
                // N1 [>= N2]   N1 >= N2
                // (all of above also defined on strings and ids)
                bool ok = true;

                if ((scopetype == number_T) || (scopetype == floatnumber_T) || (scopetype == decimalnumber_T) 
                        || (scopetype == integernumber_T)) {
                    if ((p1type != number_T) && (p1type != floatnumber_T) && (p1type != decimalnumber_T) 
                            && (p1type != integernumber_T)) {
                        ok = false;
                    }
                } else if ((typeKind (scopetype) > firstLeafKind) 
                        && (typeKind (scopetype) <= lastLeafKind)) {
                    if ((typeKind (p1type) <= firstLeafKind) && (typeKind (p1type) > lastLeafKind)) {
                        ok = false;
                    }
                } else {
                    error (context, "Scope of [>], [<], [>=] or [<=] predefined function is not a number, string or identifier", FATAL, 547);
                }

                if (!ok) {
                    error (context, "Parameter of [>], [<], [>=] or [<=] predefined function does not match scope type", FATAL, 548);
                }
            }
            break;

        case extractR:
        case shallowextractR:
            {
                // Repeat_X [^ Y] or Repeat_X [^/ Y]
                // generic extract from any scope 
                // The scope should be something of type [repeat X] for some X.
                // Replaces the scope with a sequence of all of the occurences of
                // something of type [X] in the parameter.
                string  sname;
                stringcpy (sname, *ident_idents[scopetype]);

                if (stringindex (sname, "repeat_0_") != 1) {
                    error (context, "Scope of [^] or [^/] predefined function is not a repeat", FATAL, 549);
                }
            }
            break;

        case substituteR:
            {
                // Scope [$ Old New]
                // generic substitute any type in any scope 
                if (p1type != p2type) {
                    error (context, "Parameters of [$] predefined function are of different types", FATAL, 550);
                }
            }
            break;

        case newidR:
            {
                // Id [!]
                // make any identifier unique
                if ((scopetype != id_T) && (scopetype != upperlowerid_T) && (scopetype != upperid_T) 
                        && (scopetype != lowerupperid_T) && (scopetype != lowerid_T)) {
                    error (context, "Scope of [!] predefined function is not an identifier", FATAL, 551);
                }
            }
            break;

        case underscoreR:
            {
                // Id [_ Id2]
                // concat ids with _ between
                if ((scopetype != id_T) && (scopetype != upperlowerid_T) && (scopetype != upperid_T) 
                        && (scopetype != lowerupperid_T) && (scopetype != lowerid_T)) {
                    error (context, "Scope of [_] predefined function is not an identifier", FATAL, 552);
                }

                if ((p1type != id_T) && (p1type != upperlowerid_T) && (p1type != upperid_T) 
                        && (p1type != lowerupperid_T) && (p1type != lowerid_T)) {
                    error (context, "Parameter of [_] predefined function is not an identifier", FATAL, 553);
                }
            }
            break;

        case messageR: case printR: case printattrR: case debugR: case breakpointR:
            {
                // X [message X]
                // X [print]
                // X [printattr]
                // X [debug]
                // X [breakpoint]

                // (any scope will do)
            }
            break;

        case quoteR: case unparseR:
            {
                // S [quote X]
                // S [unparse X]
                if ((typeKind (scopetype) <= firstLeafKind) || (typeKind (scopetype) > lastLeafKind)) {
                    error (context, "Scope of [quote] or [unparse] predefined function is not a string, identifier or token", FATAL, 554);
                }
            }
            break;

        case unquoteR:
            {
                // I [unquote S]
                // replaces original id or comment with the unquoted text of a string or char literal
                if ((typeKind (scopetype) <= firstLeafKind) 
                        || (typeKind (scopetype) > lastLeafKind) 
                        || (scopetype == stringlit_T) || (scopetype == charlit_T)) {
                    error (context, "Scope of [unquote] predefined function is not an identifier or token", FATAL, 555);
                }

                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [unquote] predefined function is not a string", FATAL, 556);
                }
            }
            break;

        case parseR:
            {
                // X [parse S]
                if ((typeKind (p1type) <= firstLeafKind) || (typeKind (p1type) > lastLeafKind)) {
                    error (context, "Parameter of [parse] predefined function is not a string, identifier or token", FATAL, 557);
                }
            }
            break;

        case reparseR:
            {
                // X1 [reparse X2]
                // (any scope and parameter will do)
            }
            break;

        case readR: case writeR:
            {
                // X [read S]
                if ((p1type != stringlit_T) && (p1type != charlit_T) && (p1type != id_T) 
                        && (p1type != upperlowerid_T) && (p1type != upperid_T) 
                        && (p1type != lowerupperid_T) && (p1type != lowerid_T)) {
                    error (context, "Parameter of [read] or [write] predefined function is not a string or identifier", FATAL, 558);
                }
            }
            break;

        case getR:
            {
                // X [get]
                // (any scope will do)
            }
            break;

        case getpR:
            {
                // X [getp S]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [getp] predefined function is not a string", FATAL, 559);
                }
            }
            break;

        case putR:
            {
                // X [put]
                // (any scope will do)
            }
            break;

        case putpR:
            {
                // X [putp S]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [putp] predefined function is not a string", FATAL, 560);
                }
            }
            break;

        case indexR:
            {
                // N [index S1 S2]
                if ((scopetype != number_T) && (scopetype != floatnumber_T)
                        && (scopetype != decimalnumber_T) && (scopetype != integernumber_T)) {
                    error (context, "Scope of [index] predefined function is not a number", FATAL, 561);
                }

                if ((typeKind (p1type) <= firstLeafKind) || (typeKind (p1type) > lastLeafKind)
                        || (typeKind (p2type) <= firstLeafKind) || (typeKind (p2type) > lastLeafKind)) {
                    error (context, "Parameters of [index] predefined function are not strings, identifiers or tokens", FATAL, 562);
                }
            }
            break;

        case grepR:
            {
                // S1 [grep S2]
                if ((typeKind (scopetype) <= firstLeafKind) || (typeKind (scopetype) > lastLeafKind)) {
                    error (context, "Scope of [grep] predefined function is not a string, identifier or token", FATAL, 563);
                }

                if ((typeKind (p1type) <= firstLeafKind) || (typeKind (p1type) > lastLeafKind)) {
                    error (context, "Parameter of [grep] predefined function is not a string, identifier or token", FATAL, 564);
                }
            }
            break;

        case repeatlengthR:
            {
                // N [length RX]
                if ((scopetype != number_T) && (scopetype != floatnumber_T) && (scopetype != decimalnumber_T) 
                        && (scopetype != integernumber_T)) {
                    error (context, "Scope of [length] predefined function is not a number", FATAL, 565);
                }

                if ((stringindex (*ident_idents[p1type], "repeat_") != 1) 
                        && (stringindex (*ident_idents[p1type], "list_") != 1)) {
                    error (context, "Parameter of [length] predefined function is not a [repeat] or [list]", FATAL, 566);
                }
            }
            break;

        case selectR:
            {
                // RX [select N1 N2]
                if ((stringindex (*ident_idents[scopetype], "repeat_") != 1) 
                        && (stringindex (*ident_idents[scopetype], "list_") != 1)) {
                    error (context, "Scope of [select] predefined function is not a [repeat] or [list]", FATAL, 567);
                }

                if (((p1type != number_T) && (p1type != floatnumber_T)
                            && (p1type != decimalnumber_T) && (p1type != integernumber_T))
                        || ((p2type != number_T) && (p2type != floatnumber_T)
                            && (p2type != decimalnumber_T) && (p2type != integernumber_T))) {
                    error (context, "Parameters of [select] predefined function are not numbers", FATAL, 568);
                }
            }
            break;

        case headR: case tailR:
            {
                // RX [head N]
                // RX [tail N]
                if ((stringindex (*ident_idents[scopetype], "repeat_") != 1) 
                        && (stringindex (*ident_idents[scopetype], "list_") != 1)) {
                    error (context, "Scope of [head] or [tail] predefined function is not a [repeat] or [list]", FATAL, 569);
                }

                if ((p1type != number_T) && (p1type != floatnumber_T) && (p1type != decimalnumber_T) 
                        && (p1type != integernumber_T)) {
                    error (context, "Parameter of [head] or [tail] predefined function is not a number", FATAL, 570);
                }
            }
            break;

        case globalR:
            {
                // Fake rule to hold info on global vars - never called!
                assert (false);
            }
            break;

        case quitR:
            {
                // X [quit N]
                if ((p1type != number_T) && (p1type != floatnumber_T) && (p1type != decimalnumber_T) 
                        && (p1type != integernumber_T)) {
                    error (context, "Parameter of [quit] predefined function is not a number", FATAL, 571);
                }
            }
            break;

        case fgetR:
            {
                // X [fget F]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [fget] predefined function is not a string filename", FATAL, 572);
                }
            }
            break;

        case fputR:
            {
                // X [fput F]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [fput] predefined function is not a string filename", FATAL, 573);
                }
            }
            break;

        case fputpR:
            {
                // X [fputp F S]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "First parameter of [fputp] predefined function is not a string filename", FATAL, 574);
                }

                if ((p2type != stringlit_T) && (p2type != charlit_T)) {
                    error (context, "Second parameter of [fputp] predefined function is not a string", FATAL, 575);
                }
            }
            break;

        case fopenR:
            {
                // X [fopen F M]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "First parameter of [fopen] predefined function is not a string filename", FATAL, 578);
                }

                if ((p2type != stringlit_T) && (p2type != charlit_T)) {
                    error (context, "Second parameter of [fopen] predefined function is not a string file mode", FATAL, 579);
                }
            }
            break;

        case fcloseR:
            {
                // X [fclose F]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [fclose] predefined function is not a string filename", FATAL, 576);
                }
            }
            break;

        case pragmaR:
            {
                // X [pragma S]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [pragma] predefined function is not an options string", FATAL, 577);
                }
            }
            break;

        case systemR:
            {
                // X [system S]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [system] predefined function is not a command string", FATAL, 581);
                }
            }
            break;

        case pipeR:
            {
                // S1 [pipe S2]
                if ((scopetype != stringlit_T) && (scopetype != charlit_T)) {
                    error (context, "Scope of [pipe] predefined function is not a string", FATAL, 582);
                }

                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [pipe] predefined function is not a command string", FATAL, 583);
                }
            }
            break;

        case tolowerR: case toupperR:
            {
                // I [tolower]
                if ((typeKind (scopetype) <= firstLeafKind) || (typeKind (scopetype) > lastLeafKind)) {
                    error (context, "Scope of [tolower] or [toupper] predefined function is not an identifier, string or token", FATAL, 586);
                }
            }
            break;

        case typeofR:
            {
                // I [typeof X]
                if (scopetype != id_T) {
                    error (context, "Scope of [typeof] predefined function is not of type [id]", FATAL, 584);
                }
            }
            break;

        case istypeR:
            {
                // X [istype I]
                if (p1type != id_T) {
                    error (context, "Parameter of [istype] predefined function is not of type [id]", FATAL, 585);
                }
            }
            break;

        case roundR: case truncR:
            {
                // N [round]
                // N [trunc]
                if (!((scopetype == number_T) || (scopetype == floatnumber_T) || (scopetype == decimalnumber_T) 
                        || (scopetype == integernumber_T))) {
                    error (context, "Scope of [round] or [trunc] predefined function is not a number", FATAL, 587);
                }
            }
            break;

        case getsR:
            {
                // S [gets]
                if ((scopetype != stringlit_T) && (scopetype != charlit_T)) {
                    error (context, "Scope of [gets] predefined function is not a string", FATAL, 588);
                }
            }
            break;

        case fgetsR:
            {
                // S [fgets F]
                if ((scopetype != stringlit_T) && (scopetype != charlit_T)) {
                    error (context, "Scope of [fgets] predefined function is not a string", FATAL, 589);
                }

                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [fgets] predefined function is not a string filename", FATAL, 590);
                }
            }
            break;

        case putsR:
            {
                // S [puts]
                if ((scopetype != stringlit_T) && (scopetype != charlit_T)) {
                    error (context, "Scope of [puts] predefined function is not a string", FATAL, 591);
                }
            }
            break;

        case fputsR:
            {
                // S [fputs F]
                if ((scopetype != stringlit_T) && (scopetype != charlit_T)) {
                    error (context, "Scope of [fputs] predefined function is not a string", FATAL, 592);
                }

                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "Parameter of [fputs] predefined function is not a string filename", FATAL, 593);
                }
            }
            break;

        case faccessR:
            {
                // X [faccess F M]
                // (any scope will do)
                if ((p1type != stringlit_T) && (p1type != charlit_T)) {
                    error (context, "First parameter of [faccess] predefined function is not a string filename", FATAL, 594);
                }

                if ((p2type != stringlit_T) && (p2type != charlit_T)) {
                    error (context, "Second parameter of [faccess] predefined function is not a string file mode", FATAL, 595);
                }
            }
            break;
    }
}


// Initialize the rule table with the predefined functions

struct rule_predefinedDescription {
    char name[26];
    int ruleNumber;
    int nformals;
    bool isCondition;
};

const struct rule_predefinedDescription rule_predefinedRules[nPredefinedRules] =
    {
        {"+", addR, 1, false},
        {"-", subtractR, 1, false},
        {"*", multiplyR, 1, false},
        {"/", divideR, 1, false},
        {":", substringR, 2, false},
        {"#", lengthR, 1, false},
        {">", greaterR, 1, true},
        {">=", greaterEqualR, 1, true},
        {"<", lessR, 1, true},
        {"<=", lessEqualR, 1, true},
        {"=", equalR, 1, true},
        {"~=", notEqualR, 1, true},
        {".", spliceR, 1, false},
        {",", listSpliceR, 1, false},
        {"^", extractR, 1, false},
        {"^/", shallowextractR, 1, false},
        {"$", substituteR, 2, false},
        {"!", newidR, 0, false},
        {"_", underscoreR, 1, false},
        {"message", messageR, 1, false},
        {"print", printR, 0, false},
        {"printattr", printattrR, 0, false},
        {"debug", debugR, 0, false},
        {"breakpoint", breakpointR, 0, false},
        {"quote", quoteR, 1, false},
        {"unparse", unparseR, 1, false},
        {"unquote", unquoteR, 1, false},
        {"parse", parseR, 1, false},
        {"reparse", reparseR, 1, false},
        {"read", readR, 1, false},
        {"write", writeR, 1, false},
        {"get", getR, 0, false},
        {"getp", getpR, 1, false},
        {"put", putR, 0, false},
        {"putp", putpR, 1, false},
        {"index", indexR, 2, false},
        {"grep", grepR, 1, true},
        {"length", repeatlengthR, 1, false},
        {"select", selectR, 2, false},
        {"tail", tailR, 1, false},
        {"head", headR, 1, false},
        {"_globals_", globalR, 0, false},
        {"quit", quitR, 1, false},
        {"fget", fgetR, 1, false},
        {"fput", fputR, 1, false},
        {"fputp", fputpR, 2, false},
        {"fopen", fopenR, 2, false},
        {"fclose", fcloseR, 1, false},
        {"pragma", pragmaR, 1, false},
        {"div", divR, 1, false},
        {"rem", remR, 1, false},
        {"system", systemR, 1, true},
        {"pipe", pipeR, 1, false},
        {"tolower", tolowerR, 0, false},
        {"toupper", toupperR, 0, false},
        {"typeof", typeofR, 1, false},
        {"istype", istypeR, 1, true},
        {"round", roundR, 0, false},
        {"trunc", truncR, 0, false},
        {"gets", getsR, 0, false},
        {"fgets", fgetsR, 1, false},
        {"puts", putsR, 0, false},
        {"fputs", fputsR, 1, false},
        {"faccess", faccessR, 2, true}
    };

#endif

// Initialization
void rule (void) {
    // Compute limits
    maxTotalRuleParameters = maxRules * 4;
    maxTotalRuleLocals = maxTotalRuleParameters + (maxRules * 128);
    maxTotalRuleParts = maxRules * 64;
    maxTotalRuleCalls = maxTotalRuleParts;

    // 1-origin, [1 .. maxRules]
    arrayalloc (maxRules + 1, struct ruleT, rule_rules);
    rule_rules[0].name = UNUSED;
    rule_nRules = 0;

    // 1-origin, [1 .. maxTotalRuleLocals]
    arrayalloc (maxTotalRuleLocals + 1, struct ruleLocalT, rule_ruleLocals);
    rule_ruleLocals[0].name = UNUSED;
    rule_ruleFormalCount = 0;
    rule_ruleLocalCount = maxTotalRuleParameters;

    // 1-origin, [1 .. maxTotalRuleParts]
    arrayalloc (maxTotalRuleParts + 1, struct rulePartT, rule_ruleParts);
    rule_ruleParts[0].name = UNUSED;
    rule_rulePartCount = 0;

    // 1-origin, [1 .. maxTotalRuleCalls]
    arrayalloc (maxTotalRuleCalls + 1, unsigned short, rule_ruleCalls);
    rule_ruleCalls[0] = UNUSED;
    rule_ruleCallCount = 0;

    #ifndef NOCOMPILE

    // Intiialize rule table with predefined functions
    mainRule = 0;

    for (int p = 1; p <= nPredefinedRules; p++) {
        const struct rule_predefinedDescription *px = &(rule_predefinedRules[p - 1]);
        const tokenT pname = ident_install (px->name, treeKind_id);
        const int prule = rule_enterRule (pname);

        struct ruleT *pr = &(rule_rules[prule]);
        pr->localVars.nformals = 0;
        pr->localVars.nprelocals = 0;
        pr->localVars.nlocals = 0;
        pr->localVars.localsBase = rule_ruleLocalCount;
        pr->target = NOT_FOUND;
        pr->skipName = NOT_FOUND;
        pr->skipName2 = NOT_FOUND;
        pr->skipName3 = NOT_FOUND;
        pr->prePattern.nparts = 0;
        pr->patternTP = nilTree;
        pr->postPattern.nparts = 0;
        pr->replacementTP = nilTree;
        pr->kind = ruleKind_predefined;
        pr->isCondition = px->isCondition;
        pr->defined = true;
        pr->called = false;

        for (int f = 1; f <= px->nformals; f++) {
            string dummyFormalId;
            stringprintf (dummyFormalId, "_TXL_Dummy_Formal_%d", f);
            const int dummyFormal = ident_install (dummyFormalId, treeKind_id);
            rule_enterLocalVar ("*** In TXL initialization! ***, ", &(pr->localVars), dummyFormal, any_T);
        }

        pr->localVars.nformals = pr->localVars.nlocals;
        assert (pr->localVars.nformals == px->nformals);
    }

    assert (rule_nRules == nPredefinedRules);
    #endif
}

